<template>
<div :class="[$style.container]" v-show="visible">
  <div :class="[$style.header]">
    <span>
      <span v-if="uploadingNum > 0">正在上传({{completeNum}}/{{rows.length}})</span>
      <span v-else>上传完成({{completeNum}}/{{rows.length}})</span>
    </span>
    <span>
      <span class="mr-2">
        <el-tooltip effect="dark" content="全部清空" :open-delay="750">
          <el-link :underline="false" @click="onClearAll"><i class="el-icon-brush"></i></el-link>
        </el-tooltip>
      </span>
      <span class="mx-3">
        <span>
          <el-tooltip effect="dark" content="全部恢复" :open-delay="750">
            <el-link :underline="false" @click="onResumeAll"><i class="el-icon-refresh-right"></i></el-link>
          </el-tooltip>
          <el-tooltip effect="dark" content="全部暂停" :open-delay="750">
            <el-link :underline="false" class="ml-2" @click="onCancelAll"><i class="el-icon-video-pause"></i></el-link>
          </el-tooltip>
          <el-tooltip effect="dark" content="全部开始" :open-delay="750">
            <el-link :underline="false" class="ml-2" @click="onStartAll"><i class="el-icon-video-play"></i></el-link>
          </el-tooltip>
        </span>
      </span>

      <el-dropdown trigger="click">
        <el-link :underline="false" class="mr-2"><span style="font-size: 12px;">{{statusText}}</span></el-link>
        <el-dropdown-menu slot="dropdown">
          <el-dropdown-item :class="[selectedStatusCls(item)]" v-for="(item, i) in statuses" :key="i" @click.native="onSelectStatus(item)">
            <span class="mx-3">{{item.label}}</span>
          </el-dropdown-item>
        </el-dropdown-menu>
      </el-dropdown>
      <el-link :underline="false" @click="expand = !expand" v-if="expand"><i class="el-icon-arrow-down"></i></el-link>
      <el-link :underline="false" @click="expand = !expand" v-else><i class="el-icon-arrow-up"></i></el-link>
      <el-link :underline="false" class="ml-2" @click="visible = false"><i class="el-icon-close"></i></el-link>
    </span>
  </div>
  <div :class="[$style.body]" @scroll="scrollThrottleFn" ref="body" v-show="expand">
    <ul :class="[$style.list]">
      <li :class="[$style.item, errorCls(row)]" v-for="(row, i) in renderedRows" :key="i">
        <div :class="[$style.col1]">
          <span><svg-icon icon="file"></svg-icon></span>
          <span class="ml-2" :title="row.params.name">{{row.params.name}}</span>
        </div>
        <div :class="[$style.col2]">{{row.params.file.size | size}}</div>
        <div :class="[$style.col3]">
          <a class="el-button--text" @click="onGoto(row)" :title="row.params.actualPath">{{row.params.actualPath}}</a>
        </div>
        <div :class="[$style.col4]">
          <div :class="[$style.actions]" v-if="row.status === 'success'">
            <span :class="[$style.actionText]">
              <span class="text-success"><i class="el-icon-success"></i></span><span class="ml-1">上传成功</span>
            </span>
            <span :class="[$style.actionBtn]"></span>
          </div>
          <div :class="[$style.actions]" v-else-if="row.status === 'waiting'">
             <span :class="[$style.actionText]">
              <span :class="[$style.actionText]" class="text-info"><i class="el-icon-info"></i></span><span class="ml-1">等待上传</span>
            </span>
            <span :class="[$style.actionBtn]"></span>
          </div>
          <div :class="[$style.actions]" v-else-if="row.status === 'uploading'">
            <span :class="[$style.actionText]">{{speed(row) | size}}/s</span>
            <span :class="[$style.actionBtn]" class="ml-3">
              <el-link :underline="false" @click="onCancel(row)"><i class="el-icon-video-pause"></i></el-link>
              <el-link :underline="false" @click="onAbort(row)" class="ml-1"><i class="el-icon-close"></i></el-link>
            </span>
          </div>
          <div :class="[$style.actions]" v-else-if="row.status === 'error'">
            <span :class="[$style.actionText]" :title="row.resp.message">{{row.resp.message}}</span>
            <span :class="[$style.actionBtn]" class="ml-3">
              <el-link :underline="false" @click="onResume(row)"><i class="el-icon-refresh-left"></i></el-link>
            </span>
          </div>
          <div :class="[$style.actions]" v-else-if="row.status === 'cancel'">
            <span :class="[$style.actionText]">{{row.progress}}% (已暂停)</span>
            <span :class="[$style.actionBtn]" class="ml-3">
              <el-link :underline="false" @click="onResume(row)"><i class="el-icon-video-play"></i></el-link>
              <el-link :underline="false" @click="onAbort(row)" class="ml-1"><i class="el-icon-close"></i></el-link>
            </span>
          </div>
          <div :class="[$style.actions]" v-else-if="row.status === 'abort'">
            <span :class="[$style.actionText]">已取消</span>
            <span :class="[$style.actionBtn]" class="ml-3">
              <el-link :underline="false" @click="onResume(row)"><i class="el-icon-refresh-left"></i></el-link>
            </span>
          </div>
        </div>
        <div :class="[$style.mask]" :style="[progressStyle(row)]"></div>
      </li>
    </ul>
    <div :class="[$style.panel]" :style="[listHeightStyle]"></div>
  </div>
</div>
</template>

<script lang="ts">

import { getDocByPath } from '@/api/doc'
import { FileData, UploadParams, UploadResp } from '@/plugins/oss/src/Oss'
import { throttle } from 'lodash'
import { Vue, Component, Prop, Emit, Inject, Ref, Watch } from 'vue-property-decorator'

@Component
export default class UploadManager extends Vue {
  @Ref() body!: HTMLDivElement

  expand = true

  scrollThrottleFn = throttle(this.onScroll, 50)

  startIdx = 0

  status = ''

  get statusText () {
    if (!this.status) return '全部'
    return this.statuses.find(v => v.value === this.status)?.label
  }

  statuses = [
    { label: '全部', value: '' },
    { label: '成功', value: 'success' },
    { label: '失败', value: 'error' },
    { label: '暂停', value: 'cancel' },
    { label: '取消', value: 'abort' },
    { label: '等待', value: 'waiting' }
  ]

  realtimeQueue: any[] = []

  get rows () {
    return this.$oss.queue || []
  }

  get filterRows () {
    return !this.status ? this.rows : this.rows.filter(v => v.status === this.status)
  }

  get renderedRows () {
    return this.filterRows.slice(this.startIdx, this.startIdx + 8)
  }

  get uploadingNum () {
    return this.rows.filter((v: any) => {
      return ['uploading', 'cancel'].includes(v.status)
    }).length
  }

  get completeNum () {
    return this.rows.filter((v: any) => {
      return ['success'].includes(v.status)
    }).length
  }

  get visible () {
    return this.$oss.visible
  }

  set visible (value: boolean) {
    if (!value) {
      this.expand = value
    }
    this.$oss.visible = value
  }

  get listHeightStyle () {
    return {
      height: Math.max(0, (48 * this.filterRows.length - 480)) + 'px'
    }
  }

  selectedStatusCls (row: any) {
    return this.status === row.value ? 'selected' : ''
  }

  errorCls (row: any) {
    return row.status === 'error' ? this.$style.error : ''
  }

  progressStyle (row: any) {
    if (['uploading', 'cancel'].includes(row.status)) {
      return {
        width: row.progress + '%'
      }
    }
    return {}
  }

  onGoto (row: FileData) {
    if (row.params.path === row.params.actualPath) {
      this.$router.push({ path: '/docs', query: { id: row.params.parentId as any } })
    } else {
      getDocByPath(row.params.actualPath).then(data => {
        if (!data) {
          this.$message.error('文档不存在')
          return
        }
        if (data?.type !== 'dir') {
          this.$message.error('请输入文件夹地址')
          return
        }
        this.$router.push({ path: this.$route.path, query: { id: data?.id } })
      })
    }
  }

  onCancel (row: FileData) {
    try {
      this.$oss.cancel(row)
    } catch (err: any) {
      this.$message.error(err && err.message)
    }
  }

  onAbort (row: FileData) {
    try {
      this.$oss.abort(row)
    } catch (err: any) {
      this.$message.error(err && err.message)
    }
  }

  onResume (row: FileData) {
    this.$oss.resume(row)
  }

  onResumeAll () {
    const target = this.$oss.queue.filter(v => ['error', 'abort'].includes(v.status))
    target.forEach(v => {
      this.onResume(v)
    })
  }

  onCancelAll () {
    const target = this.$oss.queue.filter(v => ['uploading', 'waiting'].includes(v.status))
    target.forEach(v => {
      if (v.status === 'uploading') {
        this.onCancel(v)
      } else {
        v.status = 'cancel'
      }
    })
  }

  onStartAll () {
    const target = this.$oss.queue.filter(v => ['cancel'].includes(v.status))
    target.forEach(v => {
      this.onResume(v)
    })
  }

  onScroll () {
    if (!this.body) return
    const top = this.body.scrollTop
    this.startIdx = Math.floor(top / 48)
  }

  onSelectStatus (row: any) {
    this.status = row.value || ''
    this.startIdx = 0
  }

  onClearAll () {
    const hasUploading = this.rows.some((v: any) => {
      return ['uploading'].includes(v.status)
    })
    if (hasUploading) {
      this.$message.info('尚有正在上传的文件，请选暂停所有上传')
      return
    }
    this.$confirm('确定清空上传列表，清空后不可恢复?', '提示').then(() => {
      this.$oss.queue = []
    }).catch(() => {
      // ignore
    })
  }

  speed (row: any) {
    if (row?.status !== 'uploading') return 0
    const id = row?.checkpoint?.uploadId
    const item = this.realtimeQueue.find((v: any) => v.id === id)
    return item?.speed || 0
  }

  created () {
    const handler = setInterval(() => {
      const items = this.renderedRows.filter(v => v.status === 'uploading')
      const queue: any[] = []
      items.forEach(v => {
        if (!v.checkpoint) return
        const { partSize, doneParts, uploadId } = v.checkpoint
        const len = doneParts.length * partSize
        const item = this.realtimeQueue.find((w: any) => w.id === uploadId) ||
          { id: uploadId, done: len, speed: 0 }
        item.speed = (len - item.done)
        item.done = len
        queue.push(item)
      })
      this.realtimeQueue = queue
    }, 1000)
    this.$once('hook:beforeDestroy', function () {
      if (handler) clearInterval(handler)
    })
  }

  $style!: any
}
</script>

<style lang="scss" module>
@import "~@/styles/vars.scss";

.container {
  position: fixed;
  z-index: 2000;
  bottom: 20px;
  right: 32px;
  border-radius: 2px;
  box-shadow: 0 0 6px 0 rgb(98 107 132 / 20%);
  background-color: $bg-color;
  width: 480px;
}

.header {
  padding: 8px 12px;
  color: $text-secondary-color;
  border-bottom: 1px solid $border-split-color;
  display: flex;
  justify-content: space-between;
}

.body {
  font-size: 12px;
  max-height: 384px;
  overflow-y: auto;
  position: relative;
}

.panel {
}

.list {
  list-style: none;
  margin: 0;
  padding: 0;
  position: sticky;
  top: 0;
  overflow: hidden;
  left: 0;
  right: 0;
  max-height: 384px;
  background-color: #fff;
}

.item {
  display: flex;
  width: 0;
  min-width: 100%;
  align-items: center;
  height: 48px;
  padding: 0 12px;
  box-sizing: border-box;
  position: relative;

  &.error {
    background-color: $color-error-lighten-5;
  }
}

.mask {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  background-color: $color-primary-lighten-5;
  width: 0;
  z-index: -1;
  transition: all ease .25s;
}

.col1 {
  width: 40%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.col2 {
  width: 12%;
  padding: 0 8px;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.col3 {
  width: 20%;
  padding: 0 8px;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;

  &:hover {
    cursor: pointer;
  }
}

.col4 {
  width: 28%;
  min-width: 120px;
  flex: 1 0 auto;
}

.actions {
  display: flex;
  width: 0;
  min-width: 100%;
}

.actionText {
  flex: 1 1 auto;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.actionBtn {
  flex: 0 0 auto;
}
</style>
